using System;
using System.Data;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Resources;
using System.IO;

namespace Curchy.PERTToolbox.PropertyGrid
{
	/// <summary>
	/// Specifies a column in which each cell contains a PropertyEditor to edit the
	/// type specified by the PropertyType property
	/// </summary>
	public class DataGridPropertyEditorColumn : DataGridColumnStyle
	{

		#region Public Properties
		/// <summary>
		/// The type of the property edited by this column
		/// </summary>
		[
		TypeConverter(typeof(TypeTypeConverter)),
		Category("Appearance"),
		Description("The type of the property that is being editing.")
		]
		public Type PropertyType
		{
			get
			{
				return mPropertyEditor.PropertyType;
			}
			set
			{
				mPropertyEditor.PropertyType= value;
			}
		}

		/// <summary>
		/// If true, store the column value as a string
		/// If false, store the column value as its actual type
		/// </summary>
		[
		Category("Appearance"),
		Description("Should the data be convertered to a string before being written back to the data source?")
		]
		public bool UseStringAsUnderlyingType
		{
			get
			{
				return mPropertyEditor.UseStringAsUnderlyingType;
			}
			set
			{
				mPropertyEditor.UseStringAsUnderlyingType= value;
			}
		}

		#endregion Public Properties

		#region Public Methods
		/// <summary>
		/// Initializes a new instance of a DataGridPropertyEditorColumn for use in a DataGrid
		/// </summary>
		public DataGridPropertyEditorColumn() : base() 
		{
			mPropertyEditor.Visible = false;
		}
		#endregion Public Properties

		#region Overriden Functionality
		/// <summary>
		/// Sets the System.Windows.Forms.DataGrid control that this column belongs to
		/// </summary>
		/// <param name="value">The System.Windows.Forms.DataGrid control that this column belongs to</param>
		protected override void SetDataGrid(DataGrid value)
		{
			base.SetDataGrid (value);
			mPropertyEditor.Font= this.DataGridTableStyle.DataGrid.Font;
			mPropertyEditor.ForeColor= this.DataGridTableStyle.ForeColor;
			mPropertyEditor.BackColor= this.DataGridTableStyle.BackColor;
		}

		/// <summary>
		/// Notifies a column that it must relinquish the focus to the control it is hosting 
		/// </summary>
		protected override void ConcedeFocus()
		{
			mPropertyEditor.ValueChanged -= new EventHandler(propertyEditorValueChanged);
			mPropertyEditor.Visible= false;
			base.ConcedeFocus ();
		}

		/// <summary>
		/// Initiates a request to interrupt an edit procedure
		/// </summary>
		/// <param name="rowNum">The row number upon which an operation is being interrupted</param>
		protected override void Abort(int rowNum)
		{
			mIsEditing = false;
			mPropertyEditor.ValueChanged -= new EventHandler(propertyEditorValueChanged);
			Invalidate();
		}

		/// <summary>
		/// Initiates a request to complete an editing procedure
		/// </summary>
		/// <param name="dataSource">The CurrencyManager for the DataGridColumnStyle</param>
		/// <param name="rowNum">The number of the row being edited</param>
		/// <returns></returns>
		protected override bool Commit(CurrencyManager dataSource, int rowNum) 
		{
			mPropertyEditor.Bounds = Rectangle.Empty;
         	mPropertyEditor.ValueChanged -= new EventHandler(propertyEditorValueChanged);
			if (!mIsEditing)
				return true;

			mIsEditing = false;

			try 
			{
				object value= mPropertyEditor.Value;
				SetColumnValueAtRow(dataSource, rowNum, value);
			} 
			catch (Exception) 
			{
				Abort(rowNum);
				return false;
			}

			Invalidate();
			return true;
		}

		/// <summary>
		/// Prepares the cell for editing a value
		/// </summary>
		/// <param name="source">The CurrencyManager for the DataGridColumnStyle</param>
		/// <param name="rowNum">The row number in this column which is being edited.</param>
		/// <param name="bounds">The Rectangle in which the control is to be sited.</param>
		/// <param name="readOnly">Value indicating whether the column is a read-only. true if the value is read-only; otherwise, false</param>
		/// <param name="instantText">The text to display in the control. </param>
		/// <param name="cellIsVisible">A value indicating whether the cell is visible. true if the cell is visible; otherwise, false.</param>
		protected override void Edit(CurrencyManager source,int rowNum,Rectangle bounds,bool readOnly,string instantText,bool cellIsVisible) 
		{
			if (! mIsEditing)
			{
				object data= GetColumnValueAtRow(source, rowNum);
				mPropertyEditor.Value= data;
			}
			if (cellIsVisible) 
			{
				mPropertyEditor.Bounds= bounds;
				mPropertyEditor.Visible = true;
				mPropertyEditor.ValueChanged += new EventHandler(propertyEditorValueChanged);
			} 
			else 
			{
				mPropertyEditor.Visible = false;
			}

			if (mPropertyEditor.Visible)
				DataGridTableStyle.DataGrid.Invalidate(bounds);
		}

		/// <summary>
		/// Returns the preferred size of a cell for a specified value
		/// </summary>
		/// <param name="g">A Graphics object that can be used in size calculations if required</param>
		/// <param name="value">The object value whose size is required</param>
		/// <returns>The preferred size</returns>
		protected override Size GetPreferredSize(Graphics g, object value) 
		{
			return new Size(100, mPropertyEditor.PreferredHeight + 4);
		}

		/// <summary>
		/// Returns the minimum height of a row
		/// </summary>
		/// <returns>The minimum height</returns>
		protected override int GetMinimumHeight() 
		{
			return mPropertyEditor.PreferredHeight + 4;
		}

		/// <summary>
		/// Returns the preferred height of a row for a given value
		/// </summary>
		/// <param name="g">A Graphics object that can be used in size calculations if required</param>
		/// <param name="value">The object value whose size is required</param>
		/// <returns>The preferred height</returns>
		protected override int GetPreferredHeight(Graphics g, 
			object value) 
		{
			return mPropertyEditor.PreferredHeight + 4;
		}

		/// <summary>
		/// Paints a DataGridColumnStyle with the specified Graphics, Rectangle, CurrencyManager and row number
		/// </summary>
		/// <param name="g">The Graphics object to draw to.</param>
		/// <param name="bounds">The bounding Rectangle to paint into.</param>
		/// <param name="source">The CurrencyManager of the System.Windows.Forms.DataGrid control the column belongs to.</param>
		/// <param name="rowNum">The number of the row in the underlying data table being referred to.</param>
		protected override void Paint(Graphics g,Rectangle bounds,CurrencyManager source,int rowNum)
		{
			Paint(g, bounds, source, rowNum, false);
		}

		/// <summary>
		/// Paints a DataGridColumnStyle with the specified Graphics, Rectangle, CurrencyManager, row number, background color, foreground color, and alignment.
		/// </summary>
		/// <param name="g">The Graphics object to draw to.</param>
		/// <param name="bounds">The bounding Rectangle to paint into.</param>
		/// <param name="source">The CurrencyManager of the System.Windows.Forms.DataGrid control the column belongs to.</param>
		/// <param name="rowNum">The number of the row in the underlying data table being referred to.</param>
		/// <param name="alignToRight">A value indicating whether to align the content to the right. true if the content is aligned to the right, otherwise, false. </param>
		protected override void Paint(Graphics g,Rectangle bounds,CurrencyManager source,int rowNum,bool alignToRight) 
		{
			Paint(g, bounds, source, rowNum, new SolidBrush(this.DataGridTableStyle.BackColor),new SolidBrush(this.DataGridTableStyle.ForeColor),alignToRight);
		}

		/// <summary>
		/// Paints a DataGridColumnStyle with the specified Graphics, Rectangle, CurrencyManager, row number, background color, foreground color, and alignment.
		/// </summary>
		/// <param name="g">The Graphics object to draw to.</param>
		/// <param name="bounds">The bounding Rectangle to paint into.</param>
		/// <param name="source">The CurrencyManager of the System.Windows.Forms.DataGrid control the column belongs to.</param>
		/// <param name="rowNum">The number of the row in the underlying data table being referred to.</param>
		/// <param name="backBrush">A Brush used to paint the background color. </param>
		/// <param name="foreBrush">A Color used to paint the foreground color.</param>
		/// <param name="alignToRight">A value indicating whether to align the content to the right. true if the content is aligned to the right, otherwise, false.</param>
		protected override void Paint(Graphics g, Rectangle bounds,	CurrencyManager source, int rowNum, Brush backBrush, Brush foreBrush,bool alignToRight) 
		{
			object data= GetColumnValueAtRow(source, rowNum);

			Rectangle rect = bounds;
			StringFormat format = new StringFormat();
			if(alignToRight) 
			{
				format.FormatFlags = StringFormatFlags.DirectionRightToLeft;
			}
			switch(this.Alignment) 
			{
				case HorizontalAlignment.Left:
					format.Alignment = StringAlignment.Near;
					break;
				case HorizontalAlignment.Right:
					format.Alignment = StringAlignment.Far;
					break;
				case HorizontalAlignment.Center:
					format.Alignment = StringAlignment.Center;
					break;
			}
			format.FormatFlags =StringFormatFlags.NoWrap;
			g.FillRectangle(backBrush, rect);
			mPropertyEditor.DrawData(g,rect,data,foreBrush,format);
			format.Dispose();
		}

		/// <summary>
		/// Use to add a PropertyEditor as a child of the DataGrid for editing purposes
		/// </summary>
		/// <param name="value">The parent DataGrid</param>
		protected override void SetDataGridInColumn(DataGrid value) 
		{
			base.SetDataGridInColumn(value);
			if (mPropertyEditor.Parent != null) 
				mPropertyEditor.Parent.Controls.Remove(mPropertyEditor);
			if (value != null) 
			{
				value.Controls.Add(mPropertyEditor);
			}
		}
		#endregion Overriden Functionality

		#region Private Functionality
		/// <summary>
		/// Event handler that is called when the Value property of the hosted PropertyEditor is changed
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void propertyEditorValueChanged(object sender, EventArgs e) 
		{
			if (this.mIsEditing)
			 	return;
			this.mIsEditing = true;
			base.ColumnStartedEditing(mPropertyEditor);
		}

		/// <summary>
		/// The hosted control used for editing when a cell in this column is in edit mode
		/// </summary>
		private PropertyEditor mPropertyEditor= new PropertyEditor();
		/// <summary>
		/// Tracks whether or not the user is editing data with the hosted control.
		/// </summary>
		private bool mIsEditing;

		#endregion Private Functionality 

	}

	/// <summary>
	/// Specifies a column in which each cell contains a DataPicture representing an Image value.
	/// </summary>
	public class DataGridDataPictureColumn : DataGridColumnStyle
	{

		#region Public Methods
		/// <summary>
		/// Initializes a new instance of a DataGridPropertyEditorColumn for use in a DataGrid
		/// </summary>
		public DataGridDataPictureColumn() : base() 
		{
			mDataPicture.Visible = false;
		}
		#endregion Public Methods

		#region Overriden Functionality

		/// <summary>
		/// Notifies a column that it must relinquish the focus to the control it is hosting 
		/// </summary>
		protected override void ConcedeFocus()
		{
			mDataPicture.ImageChanged -= new EventHandler(dataPictureImageChanged);
			mDataPicture.Visible= false;
			base.ConcedeFocus ();
		}

		/// <summary>
		/// Sets the System.Windows.Forms.DataGrid control that this column belongs to
		/// </summary>
		/// <param name="value">The System.Windows.Forms.DataGrid control that this column belongs to</param>
		protected override void SetDataGrid(DataGrid value)
		{
			base.SetDataGrid (value);
		}

		/// <summary>
		/// Initiates a request to interrupt an edit procedure
		/// </summary>
		/// <param name="rowNum">The row number upon which an operation is being interrupted</param>
		protected override void Abort(int rowNum)
		{
			mIsEditing = false;
			mDataPicture.ImageChanged -= new EventHandler(dataPictureImageChanged);
			Invalidate();
		}

		/// <summary>
		/// Initiates a request to complete an editing procedure
		/// </summary>
		/// <param name="dataSource">The CurrencyManager for the DataGridColumnStyle</param>
		/// <param name="rowNum">The number of the row being edited</param>
		/// <returns></returns>
		protected override bool Commit(CurrencyManager dataSource, int rowNum) 
		{
			mDataPicture.Bounds = Rectangle.Empty;
			mDataPicture.ImageChanged -= new EventHandler(dataPictureImageChanged);
			if (!mIsEditing)
				return true;

			mIsEditing = false;

			try 
			{
				object value= imageToByteArray(mDataPicture.Image);
				SetColumnValueAtRow(dataSource, rowNum, value);
			} 
			catch (Exception) 
			{
				Abort(rowNum);
				return false;
			}

			Invalidate();
			return true;
		}

		/// <summary>
		/// Prepares the cell for editing a value
		/// </summary>
		/// <param name="source">The CurrencyManager for the DataGridColumnStyle</param>
		/// <param name="rowNum">The row number in this column which is being edited.</param>
		/// <param name="bounds">The Rectangle in which the control is to be sited.</param>
		/// <param name="readOnly">Value indicating whether the column is a read-only. true if the value is read-only; otherwise, false</param>
		/// <param name="instantText">The text to display in the control. </param>
		/// <param name="cellIsVisible">A value indicating whether the cell is visible. true if the cell is visible; otherwise, false.</param>
		protected override void Edit(CurrencyManager source,int rowNum,Rectangle bounds,bool readOnly,string instantText,bool cellIsVisible) 
		{
			if (! mIsEditing)
			{
				object data= GetColumnValueAtRow(source, rowNum);
				if (data is byte[])
					mDataPicture.Image= byteArrayToImage((byte[])data);
				else
					mDataPicture.Image= PictureBoxContextMenu.EmptyBitmap;
			}
			if (cellIsVisible) 
			{
				mDataPicture.Bounds= bounds;
				mDataPicture.SizeMode= PictureBoxSizeMode.Normal;
				mDataPicture.Visible = true;
				mDataPicture.ImageChanged += new EventHandler(dataPictureImageChanged);
			} 
			else 
			{
				mDataPicture.Visible = false;
			}

			if (mDataPicture.Visible)
				DataGridTableStyle.DataGrid.Invalidate(bounds);
		}

		/// <summary>
		/// Returns the preferred size of a cell for a specified value
		/// </summary>
		/// <param name="g">A Graphics object that can be used in size calculations if required</param>
		/// <param name="value">The object value whose size is required</param>
		/// <returns>The preferred size</returns>
		protected override Size GetPreferredSize(Graphics g, object value) 
		{
			// return new Size(100, mDataPicture.PreferredHeight + 4);
			return new Size(100, 100+4);
		}

		/// <summary>
		/// Returns the minimum height of a row
		/// </summary>
		/// <returns>The minimum height</returns>
		protected override int GetMinimumHeight() 
		{
			return 100 + 4;
		}

		/// <summary>
		/// Returns the preferred height of a row for a given value
		/// </summary>
		/// <param name="g">A Graphics object that can be used in size calculations if required</param>
		/// <param name="value">The object value whose size is required</param>
		/// <returns>The preferred height</returns>
		protected override int GetPreferredHeight(Graphics g, 
			object value) 
		{
			return 100 + 4;
		}

		/// <summary>
		/// Paints a DataGridColumnStyle with the specified Graphics, Rectangle, CurrencyManager and row number
		/// </summary>
		/// <param name="g">The Graphics object to draw to.</param>
		/// <param name="bounds">The bounding Rectangle to paint into.</param>
		/// <param name="source">The CurrencyManager of the System.Windows.Forms.DataGrid control the column belongs to.</param>
		/// <param name="rowNum">The number of the row in the underlying data table being referred to.</param>
		protected override void Paint(Graphics g,Rectangle bounds,CurrencyManager source,int rowNum)
		{
			Paint(g, bounds, source, rowNum, false);
		}
		
		/// <summary>
		/// Paints a DataGridColumnStyle with the specified Graphics, Rectangle, CurrencyManager, row number, background color, foreground color, and alignment.
		/// </summary>
		/// <param name="g">The Graphics object to draw to.</param>
		/// <param name="bounds">The bounding Rectangle to paint into.</param>
		/// <param name="source">The CurrencyManager of the System.Windows.Forms.DataGrid control the column belongs to.</param>
		/// <param name="rowNum">The number of the row in the underlying data table being referred to.</param>
		/// <param name="alignToRight">A value indicating whether to align the content to the right. true if the content is aligned to the right, otherwise, false. </param>
		protected override void Paint(Graphics g,Rectangle bounds,CurrencyManager source,int rowNum,bool alignToRight) 
		{
			Paint(g, bounds, source, rowNum, new SolidBrush(this.DataGridTableStyle.BackColor),new SolidBrush(this.DataGridTableStyle.ForeColor),alignToRight);
		}

		/// <summary>
		/// Paints a DataGridColumnStyle with the specified Graphics, Rectangle, CurrencyManager, row number, background color, foreground color, and alignment.
		/// </summary>
		/// <param name="g">The Graphics object to draw to.</param>
		/// <param name="bounds">The bounding Rectangle to paint into.</param>
		/// <param name="source">The CurrencyManager of the System.Windows.Forms.DataGrid control the column belongs to.</param>
		/// <param name="rowNum">The number of the row in the underlying data table being referred to.</param>
		/// <param name="backBrush">A Brush used to paint the background color. </param>
		/// <param name="foreBrush">A Color used to paint the foreground color.</param>
		/// <param name="alignToRight">A value indicating whether to align the content to the right. true if the content is aligned to the right, otherwise, false.</param>
		protected override void Paint(Graphics g, Rectangle bounds,	CurrencyManager source, int rowNum, Brush backBrush, Brush foreBrush,bool alignToRight) 
		{
			object data= GetColumnValueAtRow(source, rowNum);

			System.Drawing.Image img= null;
			if (data is byte[])
				img= byteArrayToImage((byte[])data);
		
			g.FillRectangle(backBrush,bounds);
			if (img!=null)
			{
				Rectangle r= justify(bounds,img.Size,this.Alignment);
				g.DrawImage(img,r.X,r.Y,r.Width,r.Height);
			}

		}

		/// <summary>
		/// Used to add a DataPicture as a child of the DataGrid for editing purposes
		/// </summary>
		/// <param name="value">The parent DataGrid</param>
		protected override void SetDataGridInColumn(DataGrid value) 
		{
			base.SetDataGridInColumn(value);
			if (mDataPicture.Parent != null) 
				mDataPicture.Parent.Controls.Remove(mDataPicture);
			if (value != null) 
			{
				value.Controls.Add(mDataPicture);
			}
		}
		#endregion Overriden Functionality


		#region Private Functionality

		/// <summary>
		/// Event handler that is called when the Image property of the hosted DataPicture is changed
		/// </summary>
		/// <param name="sender">The sender of the event</param>
		/// <param name="e">The event arguments</param>
		private void dataPictureImageChanged(object sender, EventArgs e) 
		{
			if (this.mIsEditing)
				return;
			this.mIsEditing = true;
			base.ColumnStartedEditing(mDataPicture);
		}

		private Rectangle justify(Rectangle r,Size sz,HorizontalAlignment align)
		{
			if (sz.Width<r.Width)
			{
				switch (align)
				{
					case HorizontalAlignment.Left:
						break;
					case HorizontalAlignment.Right:
						r.X+= (r.Width-sz.Width); break;
					case HorizontalAlignment.Center:
						r.X+= ((r.Width - sz.Width)>>1); break;
				}
				r.Width= sz.Width;
			}
			if (sz.Height<r.Height)
			{
				r.Y+= ((r.Height - sz.Height)>>1);
				r.Height= sz.Height;
			}
			return r;
		}

		/// <summary>
		/// The hosted control used for editing when a cell in this column is in edit mode
		/// </summary>
		private Curchy.PERTToolbox.PropertyGrid.DataPicture mDataPicture= new Curchy.PERTToolbox.PropertyGrid.DataPicture();
		/// <summary>
		/// Tracks whether or not the user is editing data with the hosted control.
		/// </summary>
		private bool mIsEditing;

		// The two functions below could be made public and called from DataPicture.

		/// <summary>
		/// Converts the supplied byte array into an image
		/// </summary>
		/// <param name="ba">The input byte array</param>
		/// <returns>The output image</returns>
		static private Image byteArrayToImage(byte[] ba)
		{
			MemoryStream ms= new MemoryStream();
			Image rc= null;
			try
			{
				ms.Write(ba,0,ba.Length);
				rc= new Bitmap(ms);
			}
			finally
			{
				ms.Close();
			}
			return rc;
		}

		/// <summary>
		/// Converts the supplied Image into a byte array
		/// </summary>
		/// <param name="img">The input image</param>
		/// <returns>The output byte array</returns>
		static private byte[] imageToByteArray(Image img)
		{
			MemoryStream ms = new MemoryStream();
			System.Drawing.Imaging.ImageFormat format= img.RawFormat;
			if (format==System.Drawing.Imaging.ImageFormat.MemoryBmp)
				format= System.Drawing.Imaging.ImageFormat.Bmp;
			// img.Save(ms,format);
			img.Save(ms,System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders()[0],null);
			ms.Close();
			return ms.GetBuffer();
		}

		#endregion Private Functionality
	}

}
